package googlemap;

import java.awt.*;
import java.awt.image.BufferedImage;

/**
 * Created by Connor Madden.
 * User: Connor Madden
 * Date: 1/04/12
 * Time: 2:21 AM
 */
public class GoogleStaticMap
        implements MapImageI {

    private static final int
            TILE_SIZE = 256;
    private static final Point ORIGIN = new Point(0, 0);

    private int zoom;
    private int nTiles;
    private int globalDisplacementX;
    private BufferedImage image;
    private double radius;
    private LatLng centreLatLng;

    public GoogleStaticMap(BufferedImage image, int zoom, LatLng latLng) {
        this.image = image;
        this.zoom = zoom;
        this.centreLatLng = latLng;

        this.nTiles = (int) Math.pow(2, this.zoom);
        int circumference = TILE_SIZE * nTiles;
        this.radius = circumference / (2 * Math.PI);
        this.globalDisplacementX = circumference / 2;

    }

    public Point fromLatLngToPoint(LatLng latLng) {
        LatLng validatedLatLng = validateLatLng(latLng);

        Point targetGlobal = fromLatLngToGlobalPoint(new LatLng(validatedLatLng.getLat(), validatedLatLng.getLng()));
        Point centerGlobal = fromLatLngToGlobalPoint(centreLatLng);

        int x = targetGlobal.x - centerGlobal.x + (image.getWidth() / 2);
        int y = targetGlobal.y - centerGlobal.y + (image.getHeight() / 2);

        return new Point(x, y);
    }

    public LatLng fromPointToLatLng(Point point) {
        Point centerGlobal = fromLatLngToGlobalPoint(centreLatLng);

        int globalX = point.x - (image.getWidth() / 2) + centerGlobal.x;
        int globalY = point.y - (image.getHeight() / 2) + centerGlobal.y;

        return validateLatLng(fromGlobalPointToLatLng(new Point(globalX, globalY)));
    }

    private LatLng fromGlobalPointToLatLng(Point point) {
        double latRad = Math.atan(Math.sinh(point.y / radius));
        double lngRad = ((point.x - globalDisplacementX) / radius);

        double lat = Math.toDegrees(latRad);
        double lng = Math.toDegrees(lngRad);

        return validateLatLng(new LatLng(lat, lng));
    }

    private Point fromLatLngToGlobalPoint(LatLng latLng) {
        double lngRad = Math.toRadians(latLng.getLng());
        double latRad = Math.toRadians(latLng.getLat());

        int x = (int) (radius * lngRad) + globalDisplacementX;

        double sinLat = Math.sin(latRad);
        int y = (int) (((radius / 2) * Math.log((1 + sinLat) / (1 - sinLat))));
        return new Point(x, y);
    }

    /**
     * @return zoom level of the static map
     */
    public int getZoom() {
        return zoom;
    }

    /**
     * @return image of the static map
     */
    public BufferedImage getImage() {
        return image;
    }

    /**
     * @return latitude and longitude coordinates of the static map centre
     */
    public LatLng getCentreLatLng() {
        return centreLatLng;
    }

    public static LatLng validateLatLng(LatLng latLng) {
        double lat = latLng.getLat();
        double lng = latLng.getLng();

        // Loop around globe
        lat = lat % 360;
        if (Math.abs(lat) > 180) {
            lat = (360 - lat) * -1;
        }

        // Google Docs becomes inaccurate at high latitudes so has a limit of +/- 85
        if (lat > 85) {
            lat = 85;
        } else if (lat < -85) {
            lat = -85;
        }
        lat = (float) Math.round(lat * 1000000) / 1000000;  // Google Maps takes only six digits

        // Loop around globe
        lng = lng % 360;
        if (Math.abs(lng) > 180) {
            lng = (360 - lng) * -1;
        }
        lng = (float) Math.round(lng * 1000000) / 1000000;  // Google Maps takes only six digits

        return new LatLng(lat, lng);
    }
}
